use crate::docstring::Docstring;
use crate::goto::{GotoTarget, find_goto_target};
use crate::{Db, MarkupKind, RangedValue};
use ruff_db::files::{File, FileRange};
use ruff_db::parsed::parsed_module;
use ruff_text_size::{Ranged, TextSize};
use std::fmt;
use std::fmt::Formatter;
use ty_python_semantic::types::{KnownInstanceType, Type, TypeVarVariance};
use ty_python_semantic::{DisplaySettings, SemanticModel};

pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Hover<'_>>> {
    let parsed = parsed_module(db, file).load(db);
    let goto_target = find_goto_target(&parsed, offset)?;

    if let GotoTarget::Expression(expr) = goto_target {
        if expr.is_literal_expr() {
            return None;
        }
    }

    let model = SemanticModel::new(db, file);
    let docs = goto_target
        .get_definition_targets(
            file,
            db,
            ty_python_semantic::ImportAliasResolution::ResolveAliases,
        )
        .and_then(|definitions| definitions.docstring(db))
        .map(HoverContent::Docstring);

    let mut contents = Vec::new();
    if let Some(signature) = goto_target.call_type_simplified_by_overloads(&model) {
        contents.push(HoverContent::Signature(signature));
    } else if let Some(ty) = goto_target.inferred_type(&model) {
        tracing::debug!("Inferred type of covering node is {}", ty.display(db));
        contents.push(match ty {
            Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => typevar
                .bind_pep695(db)
                .map_or(HoverContent::Type(ty, None), |typevar| {
                    HoverContent::Type(Type::TypeVar(typevar), Some(typevar.variance(db)))
                }),
            Type::TypeVar(typevar) => HoverContent::Type(ty, Some(typevar.variance(db))),
            _ => HoverContent::Type(ty, None),
        });
    }
    contents.extend(docs);

    if contents.is_empty() {
        return None;
    }

    Some(RangedValue {
        range: FileRange::new(file, goto_target.range()),
        value: Hover { contents },
    })
}

pub struct Hover<'db> {
    contents: Vec<HoverContent<'db>>,
}

impl<'db> Hover<'db> {
    /// Renders the hover to a string using the specified markup kind.
    pub const fn display<'a>(&'a self, db: &'db dyn Db, kind: MarkupKind) -> DisplayHover<'db, 'a> {
        DisplayHover {
            db,
            hover: self,
            kind,
        }
    }

    fn iter(&self) -> std::slice::Iter<'_, HoverContent<'db>> {
        self.contents.iter()
    }
}

impl<'db> IntoIterator for Hover<'db> {
    type Item = HoverContent<'db>;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.contents.into_iter()
    }
}

impl<'a, 'db> IntoIterator for &'a Hover<'db> {
    type Item = &'a HoverContent<'db>;
    type IntoIter = std::slice::Iter<'a, HoverContent<'db>>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

pub struct DisplayHover<'db, 'a> {
    db: &'db dyn Db,
    hover: &'a Hover<'db>,
    kind: MarkupKind,
}

impl fmt::Display for DisplayHover<'_, '_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let mut first = true;
        for content in &self.hover.contents {
            if !first {
                self.kind.horizontal_line().fmt(f)?;
            }

            content.display(self.db, self.kind).fmt(f)?;
            first = false;
        }

        Ok(())
    }
}

#[derive(Debug, Clone)]
pub enum HoverContent<'db> {
    Signature(String),
    Type(Type<'db>, Option<TypeVarVariance>),
    Docstring(Docstring),
}

impl<'db> HoverContent<'db> {
    fn display(&self, db: &'db dyn Db, kind: MarkupKind) -> DisplayHoverContent<'_, 'db> {
        DisplayHoverContent {
            db,
            content: self,
            kind,
        }
    }
}

pub(crate) struct DisplayHoverContent<'a, 'db> {
    db: &'db dyn Db,
    content: &'a HoverContent<'db>,
    kind: MarkupKind,
}

impl fmt::Display for DisplayHoverContent<'_, '_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.content {
            HoverContent::Signature(signature) => {
                self.kind.fenced_code_block(&signature, "python").fmt(f)
            }
            HoverContent::Type(ty, variance) => {
                let variance = match variance {
                    Some(TypeVarVariance::Covariant) => " (covariant)",
                    Some(TypeVarVariance::Contravariant) => " (contravariant)",
                    Some(TypeVarVariance::Invariant) => " (invariant)",
                    Some(TypeVarVariance::Bivariant) => " (bivariant)",
                    None => "",
                };
                self.kind
                    .fenced_code_block(
                        format!(
                            "{}{variance}",
                            ty.display_with(self.db, DisplaySettings::default().multiline())
                        ),
                        "python",
                    )
                    .fmt(f)
            }
            HoverContent::Docstring(docstring) => docstring.render(self.kind).fmt(f),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::tests::{CursorTest, cursor_test};
    use crate::{MarkupKind, hover};
    use insta::assert_snapshot;
    use ruff_db::diagnostic::{
        Annotation, Diagnostic, DiagnosticFormat, DiagnosticId, DisplayDiagnosticConfig, LintName,
        Severity, Span,
    };
    use ruff_text_size::{Ranged, TextRange};

    #[test]
    fn hover_basic() {
        let test = cursor_test(
            r#"
        a = 10

        a<CURSOR>
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        Literal[10]
        ---------------------------------------------
        ```python
        Literal[10]
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:1
          |
        2 | a = 10
        3 |
        4 | a
          | ^- Cursor offset
          | |
          | source
          |
        ");
    }

    #[test]
    fn hover_function() {
        let test = cursor_test(
            r#"
        def my_func(a, b):
            '''This is such a great func!!

            Args:
                a: first for a reason
                b: coming for `a`'s title
            '''
            return 0

        my_fu<CURSOR>nc(1, 2)
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        def my_func(
            a,
            b
        ) -> Unknown
        ---------------------------------------------
        This is such a great func!!

        Args:
            a: first for a reason
            b: coming for `a`'s title

        ---------------------------------------------
        ```python
        def my_func(
            a,
            b
        ) -> Unknown
        ```
        ---
        This is such a great func!!  
          
        Args:  
        &nbsp;&nbsp;&nbsp;&nbsp;a: first for a reason  
        &nbsp;&nbsp;&nbsp;&nbsp;b: coming for `a`'s title
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:11:1
           |
         9 |     return 0
        10 |
        11 | my_func(1, 2)
           | ^^^^^-^
           | |    |
           | |    Cursor offset
           | source
           |
        ");
    }

    #[test]
    fn hover_function_def() {
        let test = cursor_test(
            r#"
        def my_fu<CURSOR>nc(a, b):
            '''This is such a great func!!

            Args:
                a: first for a reason
                b: coming for `a`'s title
            '''
            return 0
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        def my_func(
            a,
            b
        ) -> Unknown
        ---------------------------------------------
        This is such a great func!!

        Args:
            a: first for a reason
            b: coming for `a`'s title

        ---------------------------------------------
        ```python
        def my_func(
            a,
            b
        ) -> Unknown
        ```
        ---
        This is such a great func!!  
          
        Args:  
        &nbsp;&nbsp;&nbsp;&nbsp;a: first for a reason  
        &nbsp;&nbsp;&nbsp;&nbsp;b: coming for `a`'s title
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:5
          |
        2 | def my_func(a, b):
          |     ^^^^^-^
          |     |    |
          |     |    Cursor offset
          |     source
        3 |     '''This is such a great func!!
          |
        ");
    }

    #[test]
    fn hover_class() {
        let test = cursor_test(
            r#"
        class MyClass:
            '''
                This is such a great class!!

                    Don't you know?

                Everyone loves my class!!

            '''
            def __init__(self, val):
                """initializes MyClass (perfectly)"""
                self.val = val

            def my_method(self, a, b):
                '''This is such a great func!!

                Args:
                    a: first for a reason
                    b: coming for `a`'s title
                '''
                return 0

        MyCla<CURSOR>ss
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        <class 'MyClass'>
        ---------------------------------------------
        This is such a great class!!

            Don't you know?

        Everyone loves my class!!

        ---------------------------------------------
        ```python
        <class 'MyClass'>
        ```
        ---
        This is such a great class!!  
          
        &nbsp;&nbsp;&nbsp;&nbsp;Don't you know?  
          
        Everyone loves my class!!
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:24:1
           |
        22 |         return 0
        23 |
        24 | MyClass
           | ^^^^^-^
           | |    |
           | |    Cursor offset
           | source
           |
        ");
    }

    #[test]
    fn hover_class_def() {
        let test = cursor_test(
            r#"
        class MyCla<CURSOR>ss:
            '''
                This is such a great class!!

                    Don't you know?

                Everyone loves my class!!

            '''
            def __init__(self, val):
                """initializes MyClass (perfectly)"""
                self.val = val

            def my_method(self, a, b):
                '''This is such a great func!!

                Args:
                    a: first for a reason
                    b: coming for `a`'s title
                '''
                return 0
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        <class 'MyClass'>
        ---------------------------------------------
        This is such a great class!!

            Don't you know?

        Everyone loves my class!!

        ---------------------------------------------
        ```python
        <class 'MyClass'>
        ```
        ---
        This is such a great class!!  
          
        &nbsp;&nbsp;&nbsp;&nbsp;Don't you know?  
          
        Everyone loves my class!!
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:7
          |
        2 | class MyClass:
          |       ^^^^^-^
          |       |    |
          |       |    Cursor offset
          |       source
        3 |     '''
        4 |         This is such a great class!!
          |
        ");
    }

    #[test]
    fn hover_class_init() {
        let test = cursor_test(
            r#"
        class MyClass:
            '''
                This is such a great class!!

                    Don't you know?

                Everyone loves my class!!

            '''
            def __init__(self, val):
                """initializes MyClass (perfectly)"""
                self.val = val

            def my_method(self, a, b):
                '''This is such a great func!!

                Args:
                    a: first for a reason
                    b: coming for `a`'s title
                '''
                return 0

        x = MyCla<CURSOR>ss(0)
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        <class 'MyClass'>
        ---------------------------------------------
        initializes MyClass (perfectly)

        ---------------------------------------------
        ```python
        <class 'MyClass'>
        ```
        ---
        initializes MyClass (perfectly)
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:24:5
           |
        22 |         return 0
        23 |
        24 | x = MyClass(0)
           |     ^^^^^-^
           |     |    |
           |     |    Cursor offset
           |     source
           |
        ");
    }

    #[test]
    fn hover_class_init_attr() {
        let test = CursorTest::builder()
            .source(
                "mymod.py",
                r#"
        class MyClass:
            '''
                This is such a great class!!

                    Don't you know?

                Everyone loves my class!!

            '''
            def __init__(self, val):
                """initializes MyClass (perfectly)"""
                self.val = val
        "#,
            )
            .source(
                "main.py",
                r#"
        import mymod

        x = mymod.MyCla<CURSOR>ss(0)
        "#,
            )
            .build();

        assert_snapshot!(test.hover(), @r"
        <class 'MyClass'>
        ---------------------------------------------
        initializes MyClass (perfectly)

        ---------------------------------------------
        ```python
        <class 'MyClass'>
        ```
        ---
        initializes MyClass (perfectly)
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:11
          |
        2 | import mymod
        3 |
        4 | x = mymod.MyClass(0)
          |           ^^^^^-^
          |           |    |
          |           |    Cursor offset
          |           source
          |
        ");
    }

    #[test]
    fn hover_class_init_no_init_docs() {
        let test = cursor_test(
            r#"
        class MyClass:
            '''
                This is such a great class!!

                    Don't you know?

                Everyone loves my class!!

            '''
            def __init__(self, val):
                self.val = val

            def my_method(self, a, b):
                '''This is such a great func!!

                Args:
                    a: first for a reason
                    b: coming for `a`'s title
                '''
                return 0

        x = MyCla<CURSOR>ss(0)
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        <class 'MyClass'>
        ---------------------------------------------
        This is such a great class!!

            Don't you know?

        Everyone loves my class!!

        ---------------------------------------------
        ```python
        <class 'MyClass'>
        ```
        ---
        This is such a great class!!  
          
        &nbsp;&nbsp;&nbsp;&nbsp;Don't you know?  
          
        Everyone loves my class!!
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:23:5
           |
        21 |         return 0
        22 |
        23 | x = MyClass(0)
           |     ^^^^^-^
           |     |    |
           |     |    Cursor offset
           |     source
           |
        ");
    }

    #[test]
    fn hover_class_method() {
        let test = cursor_test(
            r#"
        class MyClass:
            '''
                This is such a great class!!

                    Don't you know?

                Everyone loves my class!!

            '''
            def __init__(self, val):
                """initializes MyClass (perfectly)"""
                self.val = val

            def my_method(self, a, b):
                '''This is such a great func!!

                Args:
                    a: first for a reason
                    b: coming for `a`'s title
                '''
                return 0

        x = MyClass(0)
        x.my_me<CURSOR>thod(2, 3)
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        bound method MyClass.my_method(
            a,
            b
        ) -> Unknown
        ---------------------------------------------
        This is such a great func!!

        Args:
            a: first for a reason
            b: coming for `a`'s title

        ---------------------------------------------
        ```python
        bound method MyClass.my_method(
            a,
            b
        ) -> Unknown
        ```
        ---
        This is such a great func!!  
          
        Args:  
        &nbsp;&nbsp;&nbsp;&nbsp;a: first for a reason  
        &nbsp;&nbsp;&nbsp;&nbsp;b: coming for `a`'s title
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:25:3
           |
        24 | x = MyClass(0)
        25 | x.my_method(2, 3)
           |   ^^^^^-^^^
           |   |    |
           |   |    Cursor offset
           |   source
           |
        ");
    }

    #[test]
    fn hover_member() {
        let test = cursor_test(
            r#"
        class Foo:
            a: int = 10

            def __init__(a: int, b: str):
                self.a = a
                self.b: str = b

        foo = Foo()
        foo.<CURSOR>a
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        int
        ---------------------------------------------
        ```python
        int
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:10:5
           |
         9 | foo = Foo()
        10 | foo.a
           |     -
           |     |
           |     source
           |     Cursor offset
           |
        ");
    }

    #[test]
    fn hover_function_typed_variable() {
        let test = cursor_test(
            r#"
            def foo(a, b): ...

            foo<CURSOR>
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        def foo(
            a,
            b
        ) -> Unknown
        ---------------------------------------------
        ```python
        def foo(
            a,
            b
        ) -> Unknown
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:1
          |
        2 | def foo(a, b): ...
        3 |
        4 | foo
          | ^^^- Cursor offset
          | |
          | source
          |
        ");
    }

    #[test]
    fn hover_binary_expression() {
        let test = cursor_test(
            r#"
            def foo(a: int, b: int, c: int):
                a + b ==<CURSOR> c
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        bool
        ---------------------------------------------
        ```python
        bool
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:3:5
          |
        2 | def foo(a: int, b: int, c: int):
        3 |     a + b == c
          |     ^^^^^^^^-^
          |     |       |
          |     |       Cursor offset
          |     source
          |
        ");
    }

    #[test]
    fn hover_keyword_parameter() {
        let test = cursor_test(
            r#"
            def test(ab: int):
                """my cool test

                Args:
                    ab: a nice little integer
                """
                return 0

            test(a<CURSOR>b= 123)
            "#,
        );

        // TODO: This should reveal `int` because the user hovers over the parameter and not the value.
        assert_snapshot!(test.hover(), @r"
        Literal[123]
        ---------------------------------------------
        ```python
        Literal[123]
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:10:6
           |
         8 |     return 0
         9 |
        10 | test(ab= 123)
           |      ^-
           |      ||
           |      |Cursor offset
           |      source
           |
        ");
    }

    #[test]
    fn hover_keyword_parameter_def() {
        let test = cursor_test(
            r#"
            def test(a<CURSOR>b: int):
                """my cool test

                Args:
                    ab: a nice little integer
                """
                return 0
            "#,
        );

        assert_snapshot!(test.hover(), @r#"
        int
        ---------------------------------------------
        ```python
        int
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:10
          |
        2 | def test(ab: int):
          |          ^-
          |          ||
          |          |Cursor offset
          |          source
        3 |     """my cool test
          |
        "#);
    }

    #[test]
    fn hover_union() {
        let test = cursor_test(
            r#"

            def foo(a, b):
                """The foo function"""
                return 0

            def bar(a, b):
                """The bar function"""
                return 1

            if random.choice([True, False]):
                a = foo
            else:
                a = bar

            a<CURSOR>
            "#,
        );

        assert_snapshot!(test.hover(), @r"
        (def foo(a, b) -> Unknown) | (def bar(a, b) -> Unknown)
        ---------------------------------------------
        ```python
        (def foo(a, b) -> Unknown) | (def bar(a, b) -> Unknown)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:16:1
           |
        14 |     a = bar
        15 |
        16 | a
           | ^- Cursor offset
           | |
           | source
           |
        ");
    }

    #[test]
    fn hover_overload_type_disambiguated1() {
        let test = CursorTest::builder()
            .source(
                "main.py",
                "
from mymodule import ab

a<CURSOR>b(1)
",
            )
            .source(
                "mymodule.py",
                r#"
def ab(a):
    """the real implementation!"""
"#,
            )
            .source(
                "mymodule.pyi",
                r#"
from typing import overload

@overload
def ab(a: int):
    """the int overload"""

@overload
def ab(a: str): ...
    """the str overload"""
"#,
            )
            .build();

        assert_snapshot!(test.hover(), @r"
        (a: int) -> Unknown
        ---------------------------------------------
        the int overload

        ---------------------------------------------
        ```python
        (a: int) -> Unknown
        ```
        ---
        the int overload
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:1
          |
        2 | from mymodule import ab
        3 |
        4 | ab(1)
          | ^-
          | ||
          | |Cursor offset
          | source
          |
        ");
    }

    #[test]
    fn hover_overload_type_disambiguated2() {
        let test = CursorTest::builder()
            .source(
                "main.py",
                r#"
from mymodule import ab

a<CURSOR>b("hello")
"#,
            )
            .source(
                "mymodule.py",
                r#"
def ab(a):
    """the real implementation!"""
"#,
            )
            .source(
                "mymodule.pyi",
                r#"
from typing import overload

@overload
def ab(a: int):
    """the int overload"""

@overload
def ab(a: str):
    """the str overload"""
"#,
            )
            .build();

        assert_snapshot!(test.hover(), @r#"
        (a: str) -> Unknown
        ---------------------------------------------
        the int overload

        ---------------------------------------------
        ```python
        (a: str) -> Unknown
        ```
        ---
        the int overload
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:1
          |
        2 | from mymodule import ab
        3 |
        4 | ab("hello")
          | ^-
          | ||
          | |Cursor offset
          | source
          |
        "#);
    }

    #[test]
    fn hover_overload_arity_disambiguated1() {
        let test = CursorTest::builder()
            .source(
                "main.py",
                "
from mymodule import ab

a<CURSOR>b(1, 2)
",
            )
            .source(
                "mymodule.py",
                r#"
def ab(a, b = None):
    """the real implementation!"""
"#,
            )
            .source(
                "mymodule.pyi",
                r#"
from typing import overload

@overload
def ab(a: int, b: int):
    """the two arg overload"""

@overload
def ab(a: int):
    """the one arg overload"""
"#,
            )
            .build();

        assert_snapshot!(test.hover(), @r"
        (
            a: int,
            b: int
        ) -> Unknown
        ---------------------------------------------
        the two arg overload

        ---------------------------------------------
        ```python
        (
            a: int,
            b: int
        ) -> Unknown
        ```
        ---
        the two arg overload
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:1
          |
        2 | from mymodule import ab
        3 |
        4 | ab(1, 2)
          | ^-
          | ||
          | |Cursor offset
          | source
          |
        ");
    }

    #[test]
    fn hover_overload_arity_disambiguated2() {
        let test = CursorTest::builder()
            .source(
                "main.py",
                "
from mymodule import ab

a<CURSOR>b(1)
",
            )
            .source(
                "mymodule.py",
                r#"
def ab(a, b = None):
    """the real implementation!"""
"#,
            )
            .source(
                "mymodule.pyi",
                r#"
from typing import overload

@overload
def ab(a: int, b: int):
    """the two arg overload"""

@overload
def ab(a: int):
    """the one arg overload"""
"#,
            )
            .build();

        assert_snapshot!(test.hover(), @r"
        (a: int) -> Unknown
        ---------------------------------------------
        the two arg overload

        ---------------------------------------------
        ```python
        (a: int) -> Unknown
        ```
        ---
        the two arg overload
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:1
          |
        2 | from mymodule import ab
        3 |
        4 | ab(1)
          | ^-
          | ||
          | |Cursor offset
          | source
          |
        ");
    }

    #[test]
    fn hover_overload_keyword_disambiguated1() {
        let test = CursorTest::builder()
            .source(
                "main.py",
                "
from mymodule import ab

a<CURSOR>b(1, b=2)
",
            )
            .source(
                "mymodule.py",
                r#"
def ab(a, *, b = None, c = None):
    """the real implementation!"""
"#,
            )
            .source(
                "mymodule.pyi",
                r#"
from typing import overload

@overload
def ab(a: int):
    """keywordless overload"""

@overload
def ab(a: int, *, b: int):
    """b overload"""

@overload
def ab(a: int, *, c: int):
    """c overload"""
"#,
            )
            .build();

        assert_snapshot!(test.hover(), @r"
        (
            a: int,
            *,
            b: int
        ) -> Unknown
        ---------------------------------------------
        keywordless overload

        ---------------------------------------------
        ```python
        (
            a: int,
            *,
            b: int
        ) -> Unknown
        ```
        ---
        keywordless overload
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:1
          |
        2 | from mymodule import ab
        3 |
        4 | ab(1, b=2)
          | ^-
          | ||
          | |Cursor offset
          | source
          |
        ");
    }

    #[test]
    fn hover_overload_keyword_disambiguated2() {
        let test = CursorTest::builder()
            .source(
                "main.py",
                "
from mymodule import ab

a<CURSOR>b(1, c=2)
",
            )
            .source(
                "mymodule.py",
                r#"
def ab(a, *, b = None, c = None):
    """the real implementation!"""
"#,
            )
            .source(
                "mymodule.pyi",
                r#"
from typing import overload

@overload
def ab(a: int):
    """keywordless overload"""

@overload
def ab(a: int, *, b: int):
    """b overload"""

@overload
def ab(a: int, *, c: int):
    """c overload"""
"#,
            )
            .build();

        assert_snapshot!(test.hover(), @r"
        (
            a: int,
            *,
            c: int
        ) -> Unknown
        ---------------------------------------------
        keywordless overload

        ---------------------------------------------
        ```python
        (
            a: int,
            *,
            c: int
        ) -> Unknown
        ```
        ---
        keywordless overload
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:1
          |
        2 | from mymodule import ab
        3 |
        4 | ab(1, c=2)
          | ^-
          | ||
          | |Cursor offset
          | source
          |
        ");
    }

    #[test]
    fn hover_overload_ambiguous() {
        let test = cursor_test(
            r#"
            from typing import overload

            @overload
            def foo(a: int, b):
                """The first overload"""
                return 0

            @overload
            def foo(a: str, b):
                """The second overload"""
                return 1

            if random.choice([True, False]):
                a = 1
            else:
                a = "hello"

            foo<CURSOR>(a, 2)
            "#,
        );

        assert_snapshot!(test.hover(), @r#"
        (
            a: int,
            b
        ) -> Unknown
        (
            a: str,
            b
        ) -> Unknown
        ---------------------------------------------
        The first overload

        ---------------------------------------------
        ```python
        (
            a: int,
            b
        ) -> Unknown
        (
            a: str,
            b
        ) -> Unknown
        ```
        ---
        The first overload
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:19:1
           |
        17 |     a = "hello"
        18 |
        19 | foo(a, 2)
           | ^^^- Cursor offset
           | |
           | source
           |
        "#);
    }

    #[test]
    fn hover_overload_ambiguous_compact() {
        let test = cursor_test(
            r#"
            from typing import overload

            @overload
            def foo(a: int):
                """The first overload"""
                return 0

            @overload
            def foo(a: str):
                """The second overload"""
                return 1

            if random.choice([True, False]):
                a = 1
            else:
                a = "hello"

            foo<CURSOR>(a)
            "#,
        );

        assert_snapshot!(test.hover(), @r#"
        (a: int) -> Unknown
        (a: str) -> Unknown
        ---------------------------------------------
        The first overload

        ---------------------------------------------
        ```python
        (a: int) -> Unknown
        (a: str) -> Unknown
        ```
        ---
        The first overload
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:19:1
           |
        17 |     a = "hello"
        18 |
        19 | foo(a)
           | ^^^- Cursor offset
           | |
           | source
           |
        "#);
    }

    #[test]
    fn hover_module() {
        let mut test = cursor_test(
            r#"
            import lib

            li<CURSOR>b
            "#,
        );

        test.write_file(
            "lib.py",
            r"
        '''
        The cool lib_py module!

        Wow this module rocks.
        '''
        a = 10
        ",
        )
        .unwrap();

        assert_snapshot!(test.hover(), @r"
        <module 'lib'>
        ---------------------------------------------
        The cool lib_py module!

        Wow this module rocks.

        ---------------------------------------------
        ```python
        <module 'lib'>
        ```
        ---
        The cool lib/_py module!  
          
        Wow this module rocks.
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:1
          |
        2 | import lib
        3 |
        4 | lib
          | ^^-
          | | |
          | | Cursor offset
          | source
          |
        ");
    }

    #[test]
    fn hover_module_import() {
        let mut test = cursor_test(
            r#"
            import li<CURSOR>b

            lib
            "#,
        );

        test.write_file(
            "lib.py",
            r"
        '''
        The cool lib_py module!

        Wow this module rocks.
        '''
        a = 10
        ",
        )
        .unwrap();

        assert_snapshot!(test.hover(), @r"
        The cool lib_py module!

        Wow this module rocks.

        ---------------------------------------------
        The cool lib/_py module!  
          
        Wow this module rocks.
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:8
          |
        2 | import lib
          |        ^^-
          |        | |
          |        | Cursor offset
          |        source
        3 |
        4 | lib
          |
        ");
    }

    #[test]
    fn hover_type_of_expression_with_type_var_type() {
        let test = cursor_test(
            r#"
            type Alias[T: int = bool] = list[T<CURSOR>]
            "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@Alias (invariant)
        ---------------------------------------------
        ```python
        T@Alias (invariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:34
          |
        2 | type Alias[T: int = bool] = list[T]
          |                                  ^- Cursor offset
          |                                  |
          |                                  source
          |
        ");
    }

    #[test]
    fn hover_type_of_expression_with_type_param_spec() {
        let test = cursor_test(
            r#"
            type Alias[**P = [int, str]] = Callable[P<CURSOR>, int]
            "#,
        );

        // TODO: This should be `P@Alias (<variance>)`
        assert_snapshot!(test.hover(), @r"
        typing.ParamSpec
        ---------------------------------------------
        ```python
        typing.ParamSpec
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:41
          |
        2 | type Alias[**P = [int, str]] = Callable[P, int]
          |                                         ^- Cursor offset
          |                                         |
          |                                         source
          |
        ");
    }

    #[test]
    fn hover_type_of_expression_with_type_var_tuple() {
        let test = cursor_test(
            r#"
            type Alias[*Ts = ()] = tuple[*Ts<CURSOR>]
            "#,
        );

        assert_snapshot!(test.hover(), @r"
        @Todo
        ---------------------------------------------
        ```python
        @Todo
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:31
          |
        2 | type Alias[*Ts = ()] = tuple[*Ts]
          |                               ^^- Cursor offset
          |                               |
          |                               source
          |
        ");
    }

    #[test]
    fn hover_variable_assignment() {
        let test = cursor_test(
            r#"
            value<CURSOR> = 1
            "#,
        );

        assert_snapshot!(test.hover(), @r"
        Literal[1]
        ---------------------------------------------
        ```python
        Literal[1]
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:1
          |
        2 | value = 1
          | ^^^^^- Cursor offset
          | |
          | source
          |
        ");
    }

    #[test]
    fn hover_augmented_assignment() {
        let test = cursor_test(
            r#"
            value = 1
            value<CURSOR> += 2
            "#,
        );

        // We currently show the *previous* value of the variable (1), not the new one (3).
        // Showing the new value might be more intuitive for some users, but the actual 'use'
        // of the `value` symbol here in read-context is `1`. This comment mainly exists to
        // signal that it might be okay to revisit this in the future and reveal 3 instead.
        assert_snapshot!(test.hover(), @r"
        Literal[1]
        ---------------------------------------------
        ```python
        Literal[1]
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:3:1
          |
        2 | value = 1
        3 | value += 2
          | ^^^^^- Cursor offset
          | |
          | source
          |
        ");
    }

    #[test]
    fn hover_attribute_assignment() {
        let test = cursor_test(
            r#"
            class C:
                attr: int = 1

            C.attr<CURSOR> = 2
            "#,
        );

        assert_snapshot!(test.hover(), @r"
        Literal[2]
        ---------------------------------------------
        ```python
        Literal[2]
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:5:3
          |
        3 |     attr: int = 1
        4 |
        5 | C.attr = 2
          |   ^^^^- Cursor offset
          |   |
          |   source
          |
        ");
    }

    #[test]
    fn hover_augmented_attribute_assignment() {
        let test = cursor_test(
            r#"
            class C:
                attr = 1

            C.attr<CURSOR> += 2
            "#,
        );

        // See the comment in the `hover_augmented_assignment` test above. The same
        // reasoning applies here.
        assert_snapshot!(test.hover(), @r"
        Unknown | Literal[1]
        ---------------------------------------------
        ```python
        Unknown | Literal[1]
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:5:3
          |
        3 |     attr = 1
        4 |
        5 | C.attr += 2
          |   ^^^^- Cursor offset
          |   |
          |   source
          |
        ");
    }

    #[test]
    fn hover_annotated_assignment() {
        let test = cursor_test(
            r#"
        class Foo:
            a<CURSOR>: int
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        int
        ---------------------------------------------
        ```python
        int
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:3:5
          |
        2 | class Foo:
        3 |     a: int
          |     ^- Cursor offset
          |     |
          |     source
          |
        ");
    }

    #[test]
    fn hover_annotated_assignment_with_rhs() {
        let test = cursor_test(
            r#"
        class Foo:
            a<CURSOR>: int = 1
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        Literal[1]
        ---------------------------------------------
        ```python
        Literal[1]
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:3:5
          |
        2 | class Foo:
        3 |     a: int = 1
          |     ^- Cursor offset
          |     |
          |     source
          |
        ");
    }

    #[test]
    fn hover_annotated_attribute_assignment() {
        let test = cursor_test(
            r#"
        class Foo:
            def __init__(self, a: int):
                self.a<CURSOR>: int = a
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        int
        ---------------------------------------------
        ```python
        int
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:14
          |
        2 | class Foo:
        3 |     def __init__(self, a: int):
        4 |         self.a: int = a
          |              ^- Cursor offset
          |              |
          |              source
          |
        ");
    }

    #[test]
    fn hover_type_narrowing() {
        let test = cursor_test(
            r#"
            def foo(a: str | None, b):
                '''
                    My cool func

                    Args:
                        a: hopefully a string, right?!
                '''
                if a is not None:
                    print(a<CURSOR>)
            "#,
        );

        assert_snapshot!(test.hover(), @r"
        str
        ---------------------------------------------
        ```python
        str
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:10:15
           |
         8 |     '''
         9 |     if a is not None:
        10 |         print(a)
           |               ^- Cursor offset
           |               |
           |               source
           |
        ");
    }

    #[test]
    fn hover_whitespace() {
        let test = cursor_test(
            r#"
        class C:
            <CURSOR>
            foo: str = 'bar'
        "#,
        );

        assert_snapshot!(test.hover(), @"Hover provided no content");
    }

    #[test]
    fn hover_literal_int() {
        let test = cursor_test(
            r#"
        print(
            0 + 1<CURSOR>
        )
        "#,
        );

        assert_snapshot!(test.hover(), @"Hover provided no content");
    }

    #[test]
    fn hover_literal_ellipsis() {
        let test = cursor_test(
            r#"
        print(
            .<CURSOR>..
        )
        "#,
        );

        assert_snapshot!(test.hover(), @"Hover provided no content");
    }

    #[test]
    fn hover_complex_type1() {
        let test = cursor_test(
            r#"
        from typing import Callable, Any, List
        def ab(x: int, y: Callable[[int, int], Any], z: List[int]) -> int: ...

        a<CURSOR>b
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        def ab(
            x: int,
            y: (int, int, /) -> Any,
            z: list[int]
        ) -> int
        ---------------------------------------------
        ```python
        def ab(
            x: int,
            y: (int, int, /) -> Any,
            z: list[int]
        ) -> int
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:5:1
          |
        3 | def ab(x: int, y: Callable[[int, int], Any], z: List[int]) -> int: ...
        4 |
        5 | ab
          | ^-
          | ||
          | |Cursor offset
          | source
          |
        ");
    }

    #[test]
    fn hover_complex_type2() {
        let test = cursor_test(
            r#"
        from typing import Callable, Tuple, Any
        ab: Tuple[Any, int, Callable[[int, int], Any]] = ...

        a<CURSOR>b
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        tuple[Any, int, (int, int, /) -> Any]
        ---------------------------------------------
        ```python
        tuple[Any, int, (int, int, /) -> Any]
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:5:1
          |
        3 | ab: Tuple[Any, int, Callable[[int, int], Any]] = ...
        4 |
        5 | ab
          | ^-
          | ||
          | |Cursor offset
          | source
          |
        ");
    }

    #[test]
    fn hover_complex_type3() {
        let test = cursor_test(
            r#"
        from typing import Callable, Any
        ab:  Callable[[int, int], Any] | None  = ...

        a<CURSOR>b
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        ((int, int, /) -> Any) | None
        ---------------------------------------------
        ```python
        ((int, int, /) -> Any) | None
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:5:1
          |
        3 | ab:  Callable[[int, int], Any] | None  = ...
        4 |
        5 | ab
          | ^-
          | ||
          | |Cursor offset
          | source
          |
        ");
    }

    #[test]
    fn hover_docstring() {
        let test = cursor_test(
            r#"
        def f():
            """Lorem ipsum dolor sit amet.<CURSOR>"""
        "#,
        );

        assert_snapshot!(test.hover(), @"Hover provided no content");
    }

    #[test]
    fn hover_class_typevar_variance() {
        let test = cursor_test(
            r#"
        class Covariant[T<CURSOR>]:
            def get(self) -> T:
                raise ValueError
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@Covariant (covariant)
        ---------------------------------------------
        ```python
        T@Covariant (covariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:17
          |
        2 | class Covariant[T]:
          |                 ^- Cursor offset
          |                 |
          |                 source
        3 |     def get(self) -> T:
        4 |         raise ValueError
          |
        ");

        let test = cursor_test(
            r#"
        class Covariant[T]:
            def get(self) -> T<CURSOR>:
                raise ValueError
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@Covariant (covariant)
        ---------------------------------------------
        ```python
        T@Covariant (covariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:3:22
          |
        2 | class Covariant[T]:
        3 |     def get(self) -> T:
          |                      ^- Cursor offset
          |                      |
          |                      source
        4 |         raise ValueError
          |
        ");

        let test = cursor_test(
            r#"
        class Contravariant[T<CURSOR>]:
            def set(self, x: T):
                pass
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@Contravariant (contravariant)
        ---------------------------------------------
        ```python
        T@Contravariant (contravariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:21
          |
        2 | class Contravariant[T]:
          |                     ^- Cursor offset
          |                     |
          |                     source
        3 |     def set(self, x: T):
        4 |         pass
          |
        ");

        let test = cursor_test(
            r#"
        class Contravariant[T]:
            def set(self, x: T<CURSOR>):
                pass
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@Contravariant (contravariant)
        ---------------------------------------------
        ```python
        T@Contravariant (contravariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:3:22
          |
        2 | class Contravariant[T]:
        3 |     def set(self, x: T):
          |                      ^- Cursor offset
          |                      |
          |                      source
        4 |         pass
          |
        ");
    }

    #[test]
    fn hover_function_typevar_variance() {
        let test = cursor_test(
            r#"
        def covariant[T<CURSOR>]() -> T:
            raise ValueError
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@covariant (covariant)
        ---------------------------------------------
        ```python
        T@covariant (covariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:15
          |
        2 | def covariant[T]() -> T:
          |               ^- Cursor offset
          |               |
          |               source
        3 |     raise ValueError
          |
        ");

        let test = cursor_test(
            r#"
        def covariant[T]() -> T<CURSOR>:
            raise ValueError
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@covariant (covariant)
        ---------------------------------------------
        ```python
        T@covariant (covariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:23
          |
        2 | def covariant[T]() -> T:
          |                       ^- Cursor offset
          |                       |
          |                       source
        3 |     raise ValueError
          |
        ");

        let test = cursor_test(
            r#"
        def contravariant[T<CURSOR>](x: T):
            pass
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@contravariant (contravariant)
        ---------------------------------------------
        ```python
        T@contravariant (contravariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:19
          |
        2 | def contravariant[T](x: T):
          |                   ^- Cursor offset
          |                   |
          |                   source
        3 |     pass
          |
        ");

        let test = cursor_test(
            r#"
        def contravariant[T](x: T<CURSOR>):
            pass
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@contravariant (contravariant)
        ---------------------------------------------
        ```python
        T@contravariant (contravariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:25
          |
        2 | def contravariant[T](x: T):
          |                         ^- Cursor offset
          |                         |
          |                         source
        3 |     pass
          |
        ");
    }

    #[test]
    fn hover_type_alias_typevar_variance() {
        let test = cursor_test(
            r#"
        type List[T<CURSOR>] = list[T]
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@List (invariant)
        ---------------------------------------------
        ```python
        T@List (invariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:11
          |
        2 | type List[T] = list[T]
          |           ^- Cursor offset
          |           |
          |           source
          |
        ");

        let test = cursor_test(
            r#"
        type List[T] = list[T<CURSOR>]
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@List (invariant)
        ---------------------------------------------
        ```python
        T@List (invariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:21
          |
        2 | type List[T] = list[T]
          |                     ^- Cursor offset
          |                     |
          |                     source
          |
        ");

        let test = cursor_test(
            r#"
        type Tuple[T<CURSOR>] = tuple[T]
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@Tuple (covariant)
        ---------------------------------------------
        ```python
        T@Tuple (covariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:12
          |
        2 | type Tuple[T] = tuple[T]
          |            ^- Cursor offset
          |            |
          |            source
          |
        ");

        let test = cursor_test(
            r#"
        type Tuple[T] = tuple[T<CURSOR>]
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@Tuple (covariant)
        ---------------------------------------------
        ```python
        T@Tuple (covariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:23
          |
        2 | type Tuple[T] = tuple[T]
          |                       ^- Cursor offset
          |                       |
          |                       source
          |
        ");
    }

    #[test]
    fn hover_legacy_typevar_variance() {
        let test = cursor_test(
            r#"
        from typing import TypeVar

        T<CURSOR> = TypeVar('T', covariant=True)

        def covariant() -> T:
            raise ValueError
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        typing.TypeVar
        ---------------------------------------------
        ```python
        typing.TypeVar
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:1
          |
        2 | from typing import TypeVar
        3 |
        4 | T = TypeVar('T', covariant=True)
          | ^- Cursor offset
          | |
          | source
        5 |
        6 | def covariant() -> T:
          |
        ");

        let test = cursor_test(
            r#"
        from typing import TypeVar

        T = TypeVar('T', covariant=True)

        def covariant() -> T<CURSOR>:
            raise ValueError
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@covariant (covariant)
        ---------------------------------------------
        ```python
        T@covariant (covariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:6:20
          |
        4 | T = TypeVar('T', covariant=True)
        5 |
        6 | def covariant() -> T:
          |                    ^- Cursor offset
          |                    |
          |                    source
        7 |     raise ValueError
          |
        ");

        let test = cursor_test(
            r#"
        from typing import TypeVar

        T<CURSOR> = TypeVar('T', contravariant=True)

        def contravariant(x: T):
            pass
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        typing.TypeVar
        ---------------------------------------------
        ```python
        typing.TypeVar
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:4:1
          |
        2 | from typing import TypeVar
        3 |
        4 | T = TypeVar('T', contravariant=True)
          | ^- Cursor offset
          | |
          | source
        5 |
        6 | def contravariant(x: T):
          |
        ");

        let test = cursor_test(
            r#"
        from typing import TypeVar

        T = TypeVar('T', contravariant=True)

        def contravariant(x: T<CURSOR>):
            pass
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        T@contravariant (contravariant)
        ---------------------------------------------
        ```python
        T@contravariant (contravariant)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:6:22
          |
        4 | T = TypeVar('T', contravariant=True)
        5 |
        6 | def contravariant(x: T):
          |                      ^- Cursor offset
          |                      |
          |                      source
        7 |     pass
          |
        ");
    }

    #[test]
    fn hover_binary_operator_literal() {
        let test = cursor_test(
            r#"
        result = 5 <CURSOR>+ 3
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        bound method int.__add__(value: int, /) -> int
        ---------------------------------------------
        Return self+value.

        ---------------------------------------------
        ```python
        bound method int.__add__(value: int, /) -> int
        ```
        ---
        Return self+value.
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:12
          |
        2 | result = 5 + 3
          |            -
          |            |
          |            source
          |            Cursor offset
          |
        ");
    }

    #[test]
    fn hover_binary_operator_overload() {
        let test = cursor_test(
            r#"
            from __future__ import annotations
            from typing import overload

            class Test:
                @overload
                def __add__(self, other: Test, /) -> Test:  ...
                @overload
                def __add__(self, other: Other, /) -> Test: ...
                def __add__(self, other: Test | Other, /) -> Test:
                    return self

            class Other: ...

            Test() <CURSOR>+ Test()
        "#,
        );

        // TODO: We should only show the matching overload here.
        // https://github.com/astral-sh/ty/issues/73
        assert_snapshot!(test.hover(), @r"
        (other: Test, /) -> Test
        (other: Other, /) -> Test
        ---------------------------------------------
        ```python
        (other: Test, /) -> Test
        (other: Other, /) -> Test
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:15:8
           |
        13 | class Other: ...
        14 |
        15 | Test() + Test()
           |        -
           |        |
           |        source
           |        Cursor offset
           |
        ");
    }

    #[test]
    fn hover_binary_operator_union() {
        let test = cursor_test(
            r#"
            from __future__ import annotations

            class Test:
                def __add__(self, other: Other, /) -> Other:
                    return other

            class Other:
                def __add__(self, other: Other, /) -> Other:
                    return self

            def _(a: Test | Other):
                a +<CURSOR> Other()
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        (bound method Test.__add__(other: Other, /) -> Other) | (bound method Other.__add__(other: Other, /) -> Other)
        ---------------------------------------------
        ```python
        (bound method Test.__add__(other: Other, /) -> Other) | (bound method Other.__add__(other: Other, /) -> Other)
        ```
        ---------------------------------------------
        info[hover]: Hovered content is
          --> main.py:13:7
           |
        12 | def _(a: Test | Other):
        13 |     a + Other()
           |       ^- Cursor offset
           |       |
           |       source
           |
        ");
    }

    #[test]
    fn hover_float_annotation() {
        let test = cursor_test(
            r#"
            a: float<CURSOR> = 3.14
        "#,
        );

        assert_snapshot!(test.hover(), @r"
        int | float
        ---------------------------------------------
        Convert a string or number to a floating-point number, if possible.

        ---------------------------------------------
        ```python
        int | float
        ```
        ---
        Convert a string or number to a floating-point number, if possible.
        ---------------------------------------------
        info[hover]: Hovered content is
         --> main.py:2:4
          |
        2 | a: float = 3.14
          |    ^^^^^- Cursor offset
          |    |
          |    source
          |
        ");
    }

    impl CursorTest {
        fn hover(&self) -> String {
            use std::fmt::Write;

            let Some(hover) = hover(&self.db, self.cursor.file, self.cursor.offset) else {
                return "Hover provided no content".to_string();
            };

            let source = hover.range;

            let mut buf = String::new();

            write!(
                &mut buf,
                "{plaintext}{line}{markdown}{line}",
                plaintext = hover.display(&self.db, MarkupKind::PlainText),
                line = MarkupKind::PlainText.horizontal_line(),
                markdown = hover.display(&self.db, MarkupKind::Markdown),
            )
            .unwrap();

            let config = DisplayDiagnosticConfig::default()
                .color(false)
                .format(DiagnosticFormat::Full);

            let mut diagnostic = Diagnostic::new(
                DiagnosticId::Lint(LintName::of("hover")),
                Severity::Info,
                "Hovered content is",
            );
            diagnostic.annotate(
                Annotation::primary(Span::from(source.file()).with_range(source.range()))
                    .message("source"),
            );
            diagnostic.annotate(
                Annotation::secondary(
                    Span::from(source.file()).with_range(TextRange::empty(self.cursor.offset)),
                )
                .message("Cursor offset"),
            );

            write!(buf, "{}", diagnostic.display(&self.db, &config)).unwrap();

            buf
        }
    }
}
